﻿#include "FileBrowserWidget.h"

#include "Widgets/Positioners/VerticalPositionerWidget.h"
#include "Widgets/Positioners/HorizontalPositionerWidget.h"
#include "Widgets/EditableTextWidget.h"
#include "Widgets/ButtonWidget.h"
#include "Widgets/ListWidget.h"
#include "Widgets/ImageWidget.h"
#include "Widgets/TextWidget.h"

#include "Platform/PlatformInterface.h"

#include "Asset/EngineDefaultPackage.h"

#include "Widgets/Windows/MessageBox.h"

#include "File/FileUtils.h"

RFileBrowserWidget::RFileBrowserWidget()
{
    auto Vertical=NewWidget<RVerticalPositionerWidget>(this);
    Vertical->Anchor.Fill(this);

    auto FirstLine=NewWidget<RWidget>(Vertical);
    FirstLine->Anchor.HorizontalFill(Vertical);
    FirstLine->Height=30;


    auto DirectoryButtons= NewWidget<RHorizontalPositionerWidget>(FirstLine);
    DirectoryButtons->Width=5*30+4*5;
    DirectoryButtons->Anchor.RightReference=FirstLine;
    DirectoryButtons->Anchor.RightOffset=0;
    DirectoryButtons->Anchor.VerticalFill(FirstLine);
    DirectoryButtons->Spacing=5;

    
    auto EditablePathContainer=NewWidget<RRectangleWidget>(FirstLine);
    EditablePathContainer->Anchor.VerticalFill(FirstLine);
    EditablePathContainer->Anchor.RightReference=DirectoryButtons;
    EditablePathContainer->Anchor.RightReferenceEdge=EHorizontalWidgetEdge::Left;
    EditablePathContainer->Anchor.RightOffset=5;
    EditablePathContainer->Anchor.LeftReference=FirstLine;
    EditablePathContainer->Anchor.LeftOffset=2;;

    EditablePathContainer->Fill=false;
    EditablePathContainer->BorderSize=2;
    EditablePathContainer->BorderColor=SColor::White;

    EditablePath=NewWidget<REditableTextWidget>(EditablePathContainer);
    EditablePath->Anchor.Fill(EditablePathContainer);
    EditablePath->Anchor.LeftOffset=5;
    EditablePath->Anchor.RightOffset=5;
    EditablePath->Text=U"";

    
    EditablePath->OnEditStopped.Bind(this,&RFileBrowserWidget::OnDirectoryAccepted);
    EditablePath->VerticalAlignment=ETextVerticalAlignment::Center;

    auto BackButton=NewWidget<RButtonWidget>(DirectoryButtons);
    BackButton->Text=U"⬅";
    BackButton->SetHovorColor(SColor::FromHexRGB(0x444444),SColor::HalfGreen);
    BackButton->Width=30;
    BackButton->Height=30;
    BackButton->Anchor.VerticalCenterReference=DirectoryButtons;
    BackButton->OnClicked.Bind(this,&RFileBrowserWidget::OnBackButtonClicked);

    auto ForwardButton=NewWidget<RButtonWidget>(DirectoryButtons);
    ForwardButton->Text=U"➡";
    ForwardButton->SetHovorColor(SColor::FromHexRGB(0x444444),SColor::HalfGreen);
    ForwardButton->Width=30;
    ForwardButton->Height=30;
    ForwardButton->Anchor.VerticalCenterReference=DirectoryButtons;
    ForwardButton->OnClicked.Bind(this,&RFileBrowserWidget::OnForwardButtonClicked);

    auto UpButton=NewWidget<RButtonWidget>(DirectoryButtons);
    UpButton->Text=(U"⬆");
    UpButton->SetHovorColor(SColor::FromHexRGB(0x444444),SColor::HalfGreen);
    UpButton->Width=30;
    UpButton->Height=30;
    UpButton->Anchor.VerticalCenterReference=DirectoryButtons;
    UpButton->OnClicked.Bind(this,&RFileBrowserWidget::OnUpButtonClicked);

    auto AddDirectoryButton=NewWidget<RButtonWidget>(DirectoryButtons);
    AddDirectoryButton->Icon.Texture=REngineDefaultPackage::Get()->GetAddFolderIcon();
    AddDirectoryButton->Icon.Width=25;
    AddDirectoryButton->Icon.Height=25;
    AddDirectoryButton->SetHovorColor(SColor::FromHexRGB(0x444444),SColor::HalfGreen);
    AddDirectoryButton->Width=30;
    AddDirectoryButton->Height=30;
    AddDirectoryButton->Anchor.VerticalCenterReference=DirectoryButtons;
    AddDirectoryButton->OnClicked.Bind(this,&RFileBrowserWidget::OnAddDirectoryButtonClicked);

    auto RefreshButton=NewWidget<RButtonWidget>(DirectoryButtons);
    RefreshButton->Icon.Texture=REngineDefaultPackage::Get()->GetRefreshIcon();
    RefreshButton->Icon.Width=25;
    RefreshButton->Icon.Height=25;
    RefreshButton->SetHovorColor(SColor::FromHexRGB(0x444444),SColor::HalfGreen);
    RefreshButton->Width=30;
    RefreshButton->Height=30;
    RefreshButton->Anchor.VerticalCenterReference=DirectoryButtons;
    RefreshButton->OnClicked.Bind(this,&RFileBrowserWidget::OnRefreshButtonClicked);
    

    auto SecondLine=NewWidget<RWidget>(Vertical);
    SecondLine->Anchor.HorizontalFill(Vertical);
    SecondLine->Anchor.TopReference=FirstLine;
    SecondLine->Anchor.TopReferenceEdge=EVerticalWidgetEdge::Bottom;
    SecondLine->Anchor.TopOffset=5;
    SecondLine->Anchor.BottomReference=Vertical;
    SecondLine->Anchor.BottomOffset=0;

    auto Spliter=NewWidget<RWidget>(SecondLine);
    Spliter->Anchor.VerticalFill(SecondLine);
    Spliter->Width=4;
    Spliter->X=200;
    //Spliter->FillColor=SColor::Black;

    {
        auto SpliterMouse= NewWidget<RMouseWidget>(Spliter);
        SpliterMouse->Anchor.Fill(Spliter);
        SpliterMouse->CursorShape=EMouseCursorType::ResizeHorizontal;
        SpliterMouse->Drag->bIsEnabled=true;
        SpliterMouse->Drag->Target=(Spliter);
        SpliterMouse->Drag->Direction=EDragDirection::Horizontal;
        SpliterMouse->Drag->TargetWidgetMinX=10;
        SpliterMouse->Drag->TargetWidgetMaxX=[this]()->float{
            return this->PresentSize.Y -10;
        };
    }

    FavoriteDicrectoryList=NewWidget<RListWidget>(SecondLine);
    FavoriteDicrectoryList->Name=U"FavoriteList";
    FavoriteDicrectoryList->Anchor.VerticalFill(SecondLine);
    FavoriteDicrectoryList->Anchor.LeftReference=SecondLine;
    FavoriteDicrectoryList->Anchor.LeftOffset=0;

    FavoriteDicrectoryList->Anchor.RightReference=Spliter;
    FavoriteDicrectoryList->Anchor.RightReferenceEdge=EHorizontalWidgetEdge::Left;
    FavoriteDicrectoryList->Anchor.RightOffset=0;

    FavoriteDicrectoryList->FillColor=SColor::Black;

    ContentList=NewWidget<RListWidget>(SecondLine);
    ContentList->Anchor.VerticalFill(SecondLine);
    ContentList->Anchor.LeftReference=Spliter;
    ContentList->Anchor.LeftReferenceEdge=EHorizontalWidgetEdge::Right;
    ContentList->Anchor.LeftOffset=0;

    ContentList->Anchor.RightReference=SecondLine;
    ContentList->Anchor.RightOffset=0;
    ContentList->FillColor=SColor::Black;

    
    Keyboard= NewWidget<RKeyboardWidget>(this);
    Keyboard->Anchor.Fill(this);
    Keyboard->OnKeyboardInput.Bind(this,&RFileBrowserWidget::OnKeyboardInput);
}

void RFileBrowserWidget::ShowContent(
    const SPath &InitialDirectory, 
    const TVector<CString> &InAllowedFileExtensions,
    bool InOnlyShowDirectory,
    bool InMultiSelect,
    const TVector<SFavoriteDirectory> &InExtraFavoriteDirectorys
    )
{
    //clear exist things
    History={InitialDirectory};
    CurrentDirectory=0;

    AllowedFileExtensions=InAllowedFileExtensions;
    OnlyShowDirectory = InOnlyShowDirectory;
    MultiSelectFile=InMultiSelect;

    FavoriteDirectorys=InExtraFavoriteDirectorys;
    auto RawFavoriteList= IPlatformInterface::GetPlatformInterface()->GetDefaultFavoriteList();
    for(auto& Tuple: RawFavoriteList)
    {
        RFileBrowserWidget::SFavoriteDirectory Dir;
        Dir.DisplayName= Tuple.First;
        Dir.Path=Tuple.Second;
        FavoriteDirectorys.Add(Dir);
    }

    UpdateContentList();
    UpdateFavoriteList();
    UpdateEditablePathWidget();


}

void RFileBrowserWidget::UpdateFilter(const TVector<CString> &AllowedFileExtensions)
{
    this->AllowedFileExtensions=AllowedFileExtensions;
    UpdateContentList();
}

TOptional<SPath> RFileBrowserWidget::GetSelectedDirectory() const
{
    return SelectedDirectory;
}

SPath RFileBrowserWidget::GetCurrentDirectory() const
{
    return History[CurrentDirectory];
}

TVector<SPath> RFileBrowserWidget::GetSelectedFile() const
{
    return SelectedFiles;
}

bool RFileBrowserWidget::OnBackButtonClicked(const SMouseButtonEvent&)
{
    if(CurrentDirectory<=0)
    {
        return true;
    }

    CurrentDirectory--;

    UpdateEditablePathWidget();
    UpdateContentList();

    return true;
}

bool RFileBrowserWidget::OnForwardButtonClicked(const SMouseButtonEvent&)
{
    if(CurrentDirectory>=History.Num()-1)
    {
        return true;
    }

    CurrentDirectory++;

    UpdateEditablePathWidget();
    UpdateContentList();

    return true;
}

bool RFileBrowserWidget::OnUpButtonClicked(const SMouseButtonEvent&)
{
    SPath Parent= History[CurrentDirectory].Parent();
    if(!Parent.IsEmpty())
    {
        ChangeDirectory(History[CurrentDirectory].Parent());
    }

    return true;
}

bool RFileBrowserWidget::OnAddDirectoryButtonClicked(const SMouseButtonEvent&)
{
    //create a folder in current directory
    CString FolderName= U"新建文件夹";
    int32_t Index=0;
    while(true)
    {
        SPath NewDirectory=History[CurrentDirectory]/(FolderName+CString(Index));
        if(!NewDirectory.Exists())
        {
            break;
        }
        Index++;
    }

    SPath NewDirectory=History[CurrentDirectory]/(FolderName+CString(Index));
    CString ErrorMessage=SFileUtils::CreateDirectory(NewDirectory);

    if(!ErrorMessage.IsEmpty())
    {
        //show error message
        WH::ShowMessageBox(U"错误",ErrorMessage);
        return true;
    }

    auto ItemWidget=CreateDirectoryItem(NewDirectory);

    ContentList->ScrollToWidget(ItemWidget);

    //select this item
    SelectItem(ItemWidget,true);

    StartEditSelectedItemText();
    
    return true ;
}

bool RFileBrowserWidget::OnRefreshButtonClicked(const SMouseButtonEvent&)
{
    UpdateContentList();
    UpdateEditablePathWidget();

    return true;
}

void RFileBrowserWidget::UpdateContentList()
{
    //fill the content list
    auto ContentContainer=ContentList->GetContentContainer();
    ContentContainer->ClearSubWidgets();

    auto Directorys=History[CurrentDirectory].ListSubDirectories();

    for(auto& Directory : Directorys)
    {
        CreateDirectoryItem(Directory);
    }


    if(!OnlyShowDirectory)
    {
        auto Files=History[CurrentDirectory].ListFiles();

        if(!AllowedFileExtensions.Empty())
        {
            Files=Files.Filter([this](const SPath& InFile)->bool{
                
                for(auto& Extension: AllowedFileExtensions)
                {
                    if(InFile.IsFileWithExtension(Extension))
                    {
                        return true;
                    }
                }
                return false;
            });
        }

        for(auto& File: Files)
        {
            auto ItemWidgets=CreateContentListItem(File,true);
            
            auto Text=NewWidget<RTextWidget>(ItemWidgets.ItemWidget);
            Text->Anchor.Fill(ItemWidgets.ItemWidget);
            Text->HorizontalAlignment=ETextHorizontalAlignment::Left;
            Text->VerticalAlignment=ETextVerticalAlignment::Center;
            Text->Text=File.GetFileOrDirectoryName();

            ItemWidgets.ItemMouse->OnDoubleClicked.BindLambda([this,File](const SMouseButtonEvent&)->bool{
                SelectedFiles.Add(File);
                OnFileSelectionChanged.ExecuteIfBound();
                return true;
            });
        }
    }

}

void RFileBrowserWidget::UpdateFavoriteList()
{ 
    //fill the favorite list
    FavoriteDicrectoryList->GetContentContainer()->ClearSubWidgets();
    for(auto& Dir: FavoriteDirectorys)
    {
        auto Item=NewWidget<RWidget>(FavoriteDicrectoryList->GetContentContainer());
        Item->Anchor.HorizontalFill(FavoriteDicrectoryList);
        Item->Height=30;

        auto Button=NewWidget<RRectangleWidget>(Item);
        Button->FillColor=SColor::None;
        Button->Anchor.Fill(Item);

        auto ButtonText=NewWidget<RTextWidget>(Button);
        ButtonText->Anchor.Fill(Button);
        ButtonText->Text=Dir.DisplayName;
        ButtonText->HorizontalAlignment=ETextHorizontalAlignment::Left;
        auto ButtonMouse=NewWidget<RMouseWidget>(Button);
        ButtonMouse->Anchor.Fill(Button);

        auto WeakButton=RRectangleWidgetWeakPtr(Button);
        ButtonMouse->OnEntered.BindLambda([WeakButton](){
            WeakButton->FillColor=SColor::FromHexRGB(0x444444);
        });
        ButtonMouse->OnExited.BindLambda([WeakButton](){
            WeakButton->FillColor=SColor::None;
        });
        ButtonMouse->OnClicked.BindLambda([this,Dir](const SMouseButtonEvent&)->bool{
            ChangeDirectory(Dir.Path);
            return true;
        });

    }
}

void RFileBrowserWidget::UpdateEditablePathWidget()
{
    EditablePath->CancelEdit();
    EditablePath->Text=History[CurrentDirectory].ToString();
}

void RFileBrowserWidget::ChangeDirectory(const SPath &NewDirectory)
{
    assert(!NewDirectory.IsEmpty()&& NewDirectory.IsDirectory() && NewDirectory.Exists());

    //当前在路径输入框中输入的路径可能并不是当前目录的路径
    UpdateEditablePathWidget();

    if(NewDirectory==History[CurrentDirectory])
    {
        return;
    }

    History.Resize(CurrentDirectory+1,false);
    History.Add(NewDirectory);
    CurrentDirectory++;


    UpdateContentList();

    OnDirectoryChanged.ExecuteIfBound();
}

RFileBrowserWidget::SContentListItemWidget RFileBrowserWidget::CreateContentListItem(const SPath& Path,bool bIsFile)
{
    auto Item=NewWidget<RRectangleWidget>(ContentList->GetContentContainer());
    Item->Anchor.HorizontalFill(ContentList->GetContentContainer());
    Item->Height=30;

    Item->Tags->UpdateTag(U"Path",Path);
    Item->Tags->UpdateTag(U"IsFile",bIsFile);

    //item have 2 attributes:  IsHorvored and IsSelected
    Item->Tags->UpdateTag(U"IsHorvored",false);
    Item->Tags->UpdateTag(U"IsSelected",false);

    TWeakObjectPtr<RRectangleWidget> ItemWeak=Item;
    Item->FillColor=[ItemWeak]()->SColor
    {
        bool IsSelected=AnyCast<bool>(ItemWeak->Tags->GetTag(U"IsSelected"));
        if(IsSelected)
        {
            return SColor::HalfGreen;
        }

        bool IsHorvored=AnyCast<bool>(ItemWeak->Tags->GetTag(U"IsHorvored"));
        if(IsHorvored)
        {
            return SColor::FromHexRGB(0x444444);
        }
        return SColor::None;
    };
    
    auto Mouse = NewWidget<RMouseWidget>(Item);
    Mouse->Anchor.Fill(Item);
    Mouse->OnEntered.BindLambda([ItemWeak]()->bool{
        ItemWeak->Tags->UpdateTag(U"IsHorvored",true);
        return true;
    });

    Mouse->OnExited.BindLambda([ItemWeak]()->bool{
        ItemWeak->Tags->UpdateTag(U"IsHorvored",false);
        return true;
    });

    Mouse->OnClicked.BindLambda([ItemWeak,this](const SMouseButtonEvent&)->bool{
        
        //select this item
        SelectItem(ItemWeak);
        return true;
    });

    SContentListItemWidget Result;
    Result.ItemWidget=Item;
    Result.ItemMouse=Mouse;

    return Result;
}

void RFileBrowserWidget::SelectItem(RWidgetPtr ItemWidget,bool ForceUnselectOthers)
{
    bool IsFile=AnyCast<bool>(ItemWidget->Tags->GetTag(U"IsFile"));
    //unselect all other items
    if(ForceUnselectOthers
        ||!IsFile
        ||!MultiSelectFile 
        || !(IPlatformInterface::GetPlatformInterface()->IsKeyDown(EKey::LControl) || IPlatformInterface::GetPlatformInterface()->IsKeyDown(EKey::RControl))
    )
    {
        //unselect all other items
        auto ContentContainer=ItemWidget->GetParent();
        for(auto& SubWidget: ContentContainer->GetSubWidgets())
        {
            if(SubWidget!=ItemWidget)
            {
                UnselectItem(SubWidget);
            }
        }
    }

    ItemWidget->Tags->UpdateTag(U"IsSelected",true);

    Keyboard->Focus();

    if(IsFile)
    {
        SelectedFiles.Add(AnyCast<SPath>(ItemWidget->Tags->GetTag(U"Path")));
        OnFileSelectionChanged.ExecuteIfBound();
    }
    else
    {
        SelectedDirectory=AnyCast<SPath>(ItemWidget->Tags->GetTag(U"Path"));
        OnFileSelectionChanged.ExecuteIfBound();
    }    

}

void RFileBrowserWidget::UnselectItem(RWidgetPtr ItemWidget)
{
    ItemWidget->Tags->UpdateTag(U"IsSelected",false);

    bool IsFile=AnyCast<bool>(ItemWidget->Tags->GetTag(U"IsFile"));
    if(IsFile)
    {
        SelectedFiles.Remove(AnyCast<SPath>(ItemWidget->Tags->GetTag(U"Path")));
        OnFileSelectionChanged.ExecuteIfBound();
    }
    else
    {
        SelectedDirectory=TOptional<SPath>();
        OnFileSelectionChanged.ExecuteIfBound();
    }
}

bool RFileBrowserWidget::OnKeyboardInput(const SKeyboardEvent &BoardkeyEvent)
{
    if(BoardkeyEvent.Key==EKey::F2
        &&BoardkeyEvent.KeyState==EKeyState::Pressed
    )
    {
        return StartEditSelectedItemText();

    }

    return false;
}

bool RFileBrowserWidget::StartEditSelectedItemText()
{
    //find selected item
    auto ContentContainer=ContentList->GetContentContainer();

    //check if there is only one item selected
    TSharedObjectPtr<RWidget> SelectedItem;
    for(auto& SubWidget: ContentContainer->GetSubWidgets())
    {
        if(AnyCast<bool>(SubWidget->Tags->GetTag(U"IsSelected")))
        {
            if(SelectedItem)
            {
                //more than one item selected
                return false;
            }

            SelectedItem=SubWidget;
        }
    }

    if(!SelectedItem)
    {
        //no item selected
        return false;
    }

    //hide the text
    TSharedObjectPtr<RTextWidget> Text;
    for(auto& SubWidget: SelectedItem->GetSubWidgets())
    {
        if(SubWidget->IsA<RTextWidget>())
        {
            Text=SubWidget;
            break;
        }
    }
    Text->Visible=false;

    //start edit
    auto EditText=NewWidget<REditableTextWidget>(SelectedItem);
    EditText->Anchor.Fill(Text);
    SPath Path=AnyCast<SPath>(SelectedItem->Tags->GetTag(U"Path"));
    EditText->Text=Text->Text.Get();
    EditText->StartEdit();


    TWeakObjectPtr<RTextWidget> TextWeak=Text;
    TWeakObjectPtr<REditableTextWidget> EditTextWeak=EditText;
    TWeakObjectPtr<RWidget> SelectedItemWeak=SelectedItem;
    EditText->OnEditStopped.BindLambda([this,TextWeak,EditTextWeak,Path,SelectedItemWeak](ETextEditStopReason Reason,const CString& Text){
        if(Reason==ETextEditStopReason::LostFocus)
        {
            //restore the path
            TextWeak->Visible=true;
            EditTextWeak->RemoveFromParent();
            return;
        }
                    
        CString ErrorMessage=SFileUtils::RenameFileOrDirectory(Path,Text);

        if(!ErrorMessage.IsEmpty())
        {
            //show error message
            WH::ShowMessageBox(U"错误",ErrorMessage);

            //restore the path
            TextWeak->Visible=true;
            EditTextWeak->RemoveFromParent();
            return;
        }

        //update the path
        TextWeak->Text=Text;
        TextWeak->Visible=true;

        SPath NewPath=Path.Parent()/TextWeak->Text.Get();
        SelectedItemWeak->Tags->UpdateTag(U"Path",NewPath);
        EditTextWeak->Visible=false;
        EditTextWeak->RemoveFromParent();

    });

    return true;
}

RWidgetPtr RFileBrowserWidget::CreateDirectoryItem(const SPath &Directory)
{
    auto ItemWidgets=CreateContentListItem(Directory,false);

    auto Icon= NewWidget<RImageWidget>(ItemWidgets.ItemWidget);
    Icon->Anchor.LeftReference=ItemWidgets.ItemWidget;
    Icon->Anchor.LeftOffset=5;
    Icon->Anchor.VerticalCenterReference=ItemWidgets.ItemWidget;
    Icon->Anchor.VerticalCenterOffset=0;
    Icon->Width=20;
    Icon->Height=20;
    Icon->Image=REngineDefaultPackage::Get()->GetFolderIcon();

    auto Text=NewWidget<RTextWidget>(ItemWidgets.ItemWidget);
    Text->Anchor.VerticalFill(ItemWidgets.ItemWidget);
    Text->Anchor.LeftReference=Icon;
    Text->Anchor.LeftReferenceEdge=EHorizontalWidgetEdge::Right;
    Text->Anchor.LeftOffset=5;
    
    Text->Anchor.RightReference=ItemWidgets.ItemWidget;
    Text->HorizontalAlignment=ETextHorizontalAlignment::Left;
    Text->VerticalAlignment=ETextVerticalAlignment::Center;
    Text->Text=Directory.GetFileOrDirectoryName();

    ItemWidgets.ItemMouse->OnDoubleClicked.BindLambda([this,Directory](const SMouseButtonEvent&)->bool{
        ChangeDirectory(Directory);
        return true;
    });

    return ItemWidgets.ItemWidget;
}

void RFileBrowserWidget::OnDirectoryAccepted(ETextEditStopReason Reason,const CString& InText)
{
    if(Reason==ETextEditStopReason::LostFocus)
    {
        //restore the path
        UpdateEditablePathWidget();
        return;
    }

    SPath NewPath=SPath(InText).ToAbsolute();

    if(NewPath.Exists())
    {
        if(NewPath.IsDirectory())
        {
            ChangeDirectory(NewPath);
            return;
        }
        else
        {
            //show error message
            WH::ShowMessageBox(U"错误",U"输入的路径不是一个文件夹");

            //让用户继续编辑
            EditablePath->Text= InText;
            EditablePath->StartEdit();
            return;
        }
    }
    else
    {
        //show error message
        WH::ShowMessageBox(U"错误",U"输入的路径不存在");

        //让用户继续编辑
        EditablePath->Text= InText;
        EditablePath->StartEdit();
        return;
    }
}

